package ch.trackedbean.internal;

import java.util.*;

/**
 * Helper class for analyzing/visiting the structure of a class.
 * 
 * @author M. Hautle
 */
public class ClassStructureHelper {
    /**
     * Hidden constructor.
     */
    private ClassStructureHelper() {
    }

    /**
     * Visits the structure of the given class (including itself).<br>
     * The visiting is done in breadth first manner:
     * <ul>
     * <li>The class</li>
     * <li>The directly implemented interfaces</li>
     * <li>The direct super interfaces of every implemented interfaces</li>
     * <li>The direct super interfaces of every super interfaces</li>
     * <li>... and so on for the whole interface structure</li>
     * </ul>
     * The steps above were then repeated for every super class.
     * 
     * @param <T> The return value type
     * @param c The class to visit
     * @param v The visitor
     * @return The return value of the first non <code>null</code> of a {@link IVisitor#visitClass(Class)} or {@link IVisitor#visitInterface(Class)} call or
     *         null if no non <code>null</code> result was returned
     */
    public static <T> T visit(Class c, IVisitor<T> v) {
        for (Class s = c; s != null; s = s.getSuperclass()) {
            T res = v.visitClass(s);
            if (res != null)
                return res;
            res = visitInterfaces(s.getInterfaces(), v);
            if (res != null)
                return res;
        }
        return null;
    }

    /**
     * Visits the given interfaces and their superinterfaces in breath first manner.
     * 
     * @param <T> The return value type
     * @param ints The interfaces
     * @param v The visitor
     * @return The return value of the first non <code>null</code> of a {@link IVisitor#visitClass(Class)} or {@link IVisitor#visitInterface(Class)} call or
     *         null if no non <code>null</code> result was returned
     */
    private static <T> T visitInterfaces(Class[] ints, IVisitor<T> v) {
        for (Class i : ints) {
            final T res = v.visitInterface(i);
            if (res != null)
                return res;
        }
        for (Class i : ints) {
            final T res = visitInterfaces(i.getInterfaces(), v);
            if (res != null)
                return res;
        }
        return null;
    }

    /**
     * Visits the structure of the given class (including itself).<br>
     * This method has the same behavior as {@link #visit(Class, IVisitor)}, with one difference:<br>
     * The {@link IVisitor#visitInterface(Class)} method will only be called once for each interface class. Independent of the number of it's 'implementations'
     * in the class structure. hierarchy.
     * 
     * @param <T> The return value type
     * @param c The class to visit
     * @param v The visitor
     * @return The return value of the first non <code>null</code> of a {@link IVisitor#visitClass(Class)} or {@link IVisitor#visitInterface(Class)} call or
     *         null if no non <code>null</code> result was returned
     */
    public static <T> T visitUnique(Class c, IVisitor<T> v) {
        final Set<Class> visited = new HashSet<Class>();
        for (Class s = c; s != null; s = s.getSuperclass()) {
            T res = v.visitClass(s);
            if (res != null)
                return res;
            res = visitInterfacesUnique(s.getInterfaces(), v, visited);
            if (res != null)
                return res;
        }
        return null;
    }

    /**
     * Visits the given interfaces and their superinterfaces in breath first manner.<br>
     * This method does not (re)processes interfaces already processed (so listed in the <code>visited</code> set).
     * 
     * @param <T> The return value type
     * @param ints The interfaces
     * @param v The visitor
     * @param visited The already visited interfaces
     * @return The return value of the first non <code>null</code> of a {@link IVisitor#visitClass(Class)} or {@link IVisitor#visitInterface(Class)} call or
     *         null if no non <code>null</code> result was returned
     */
    private static <T> T visitInterfacesUnique(Class[] ints, IVisitor<T> v, Set<Class> visited) {
        for (Class i : ints) {
            if (visited.contains(i))
                continue;
            final T res = v.visitInterface(i);
            if (res != null)
                return res;
        }
        for (Class i : ints) {
            if (!visited.add(i))
                continue;
            final T res = visitInterfacesUnique(i.getInterfaces(), v, visited);
            if (res != null)
                return res;
        }
        return null;
    }

    /**
     * The visitor for the class structure.
     * 
     * @param <T> The return value type
     * @author M. Hautle
     */
    public interface IVisitor<T> {
        /**
         * Visits the given interface.
         * 
         * @param c The interface
         * @return The result to return or <code>null</code> if the visit should continue
         */
        T visitInterface(Class c);

        /**
         * Visits the given class.
         * 
         * @param c The class
         * @return The result to return or <code>null</code> if the visit should continue
         */
        T visitClass(Class c);
    }
}
